/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.dq.core.context;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.unidata.mdm.core.type.data.DataRecord;
import org.unidata.mdm.dq.core.type.io.DataQualityInput;
import org.unidata.mdm.dq.core.type.model.instance.MappingSetElement;
import org.unidata.mdm.system.context.CommonRequestContext;
import org.unidata.mdm.system.type.namespace.NameSpace;
import org.unidata.mdm.system.util.NameSpaceUtils;


/**
 * Data quality context.
 *
 * @author ilya.bykov
 */
public class DataQualityContext extends CommonRequestContext {
    /**
     * SVUID.
     */
    private static final long serialVersionUID = -7404007503023231104L;
    /**
     * Source system name.
     */
    private final String sourceSystem;
    /**
     * The rule set to execute.
     */
    private final transient List<MappingSetElement> mappings;
    /**
     * The payload to save.
     */
    private final transient Object payload;
    /**
     * The input.
     */
    private final DataQualityInput input;
    /**
     * Instantiates a new DQ context.
     */
    private DataQualityContext(DataQualityContextBuilder b) {
        super(b);
        this.sourceSystem = b.sourceSystem;
        this.mappings = CollectionUtils.isNotEmpty(b.mappings) ? b.mappings : Collections.emptyList();
        this.payload = b.payload;
        this.input = new DataQualityInput(b.input);
    }
    /**
     * @return the sourceSystem
     */
    public String getSourceSystem() {
        return sourceSystem;
    }
    /**
     * @return the mappings
     */
    public List<MappingSetElement> getMappings() {
        return mappings;
    }
    /**
     * @return the input
     */
    public DataQualityInput getInput() {
        return input;
    }
    /**
     * @return the payload
     */
    @SuppressWarnings("unchecked")
    public<T> T getPayload() {
        return (T) payload;
    }
    /**
     * Returns true, if some payload is set.
     * @return true, if set
     */
    public boolean hasPayload() {
        return Objects.nonNull(payload);
    }
    /**
     * The usual builder.
     * @return builder
     */
    public static DataQualityContextBuilder builder() {
        return new DataQualityContextBuilder();
    }
    /**
     * @author Mikhail Mikhailov
     * The ususal builder class.
     */
    public static class DataQualityContextBuilder extends CommonRequestContextBuilder<DataQualityContextBuilder> {
        /**
         * Source system name.
         */
        private String sourceSystem;
        /**
         * The payload to save.
         */
        private Object payload;
        /**
         * The rule set to execute.
         */
        private List<MappingSetElement> mappings;
        /**
         * The input.
         */
        private Map<String, List<Pair<String, DataRecord>>> input;
        /**
         * Constructor.
         */
        private DataQualityContextBuilder() {
            super();
        }
        /**
         * @param sourceSystem the sourceSystem to set
         */
        public DataQualityContextBuilder sourceSystem(String sourceSystem) {
            this.sourceSystem = sourceSystem;
            return this;
        }
        /**
         * RMSE rules.
         * @param mapping the rules mapping
         * @return self
         */
        public DataQualityContextBuilder mappings(Collection<MappingSetElement> mappings) {
            if (CollectionUtils.isNotEmpty(mappings)) {
                for (MappingSetElement mse : mappings) {
                    mapping(mse);
                }
            }
            return this;
        }
        /**
         * RMSE rules.
         * @param mapping the rules mapping
         * @return self
         */
        public DataQualityContextBuilder mapping(MappingSetElement mapping) {
            if (Objects.nonNull(mapping)) {
                if (Objects.isNull(mappings)) {
                    mappings = new ArrayList<>();
                }
                this.mappings.add(mapping);
            }
            return this;
        }
        /**
         * Adds input (no detailed namespace).
         * Variant for no detailed namespace (resolved to {@link NameSpace#GLOBAL_NAMESPACE_ID} and no type name.
         * @param id the record's ID (must not be null)
         * @param record the record
         * @return self
         */
        public DataQualityContextBuilder input(String id, DataRecord record) {
            return input(null, null, id, record);
        }
        /**
         * Adds input (no detailed namespace).
         * Variant for no detailed namespace (resolved to {@link NameSpace#GLOBAL_NAMESPACE_ID} but defined type name.
         * @param typeName the type name
         * @param id the record's ID (must not be null)
         * @param record the record
         * @return self
         */
        public DataQualityContextBuilder input(String typeName, String id, DataRecord record) {
            return input(null, typeName, id, record);
        }
        /**
         * Adds input (no detailed type name).
         * @param ns the namespace
         * @param id the record's ID (must not be null)
         * @param record the record
         * @return self
         */
        public DataQualityContextBuilder input(NameSpace ns, String id, DataRecord record) {
            return input(ns, null, id, record);
        }
        /**
         * Adds input.
         * @param ns the namespace
         * @param typeName the type name
         * @param id the record's ID (must not be null)
         * @param record the record
         * @return self
         */
        public DataQualityContextBuilder input(NameSpace ns, String typeName, String id, DataRecord record) {
            if (Objects.nonNull(record)) {

                Objects.requireNonNull(id, "Record's ID must not be null.");
                if (Objects.isNull(input)) {
                    input = new HashMap<>();
                }
                final String key = NameSpaceUtils.join(ns, typeName);
                this.input.computeIfAbsent(key, k -> new ArrayList<>()).add(Pair.of(id, record));
            }
            return this;
        }
        /**
         * Sets payload.
         * @param payload the payload
         * @return self
         */
        public<T> DataQualityContextBuilder payload(T payload) {
            this.payload = payload;
            return self();
        }
        /**
         * Build.
         * @return ctx
         */
        @Override
        public DataQualityContext build() {
            return new DataQualityContext(this);
        }
    }
}
